golang struct 转 json 时如何忽略部分字段

将字段首字母改为小写或添加 json:"-" 标签能够在 json.Marshal() 时忽略指定字段,但此文章想讨论的是在不修改原 struct 结构的前提下过忽略部分字段的方法。

为什么会有这种需求呢?如果要转为 json 的 struct 定义在依赖包中或有其他地方需要输出这些字段,这时就不能修改原 struct。

定义只有必要字段的新 struct

定义新 struct,在新 struct 中只保留必要字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type source struct {
A int `json:"a"`
B int `json:"b"`
}
type target struct {
A int `json:"a"`
}

func main() {
s := source{A: 1, B: 2}
t := target{A: s.A}

bytes, _ := json.Marshal(t)
fmt.Println(string(bytes))
}

这是最容易想到的方法,但当字段较多或 struct 较复杂(比如嵌套多层其他 struct)时就会有很多将 source 的字段赋值给 target 的操作。

定义 tag 不同结构相同的 struct

此方法仅适用 golang 1.8+,1.8 之后结构相同但 tag 不同的 struct 之间能够强制转换。利用这个特性,我们能简化掉字段赋值的操作。完整示例点击这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type source struct {
A int `json:"a"`
B int `json:"b"` // 注意这里
}
type target struct {
A int `json:"a"`
B int `json:"-"` // 注意这里
}

func main() {
s := source{A: 1, B: 2}
t := target(s)

bytes, _ := json.Marshal(t)
fmt.Println(string(bytes))
}

通过 map 转换

json 和 struct 转换时,struct 必须提前定义好。如果不知道结构或不想定义 struct,可以将其转为 interface{}

1
2
3
4
5
6
func main() {
bytes := []byte(`{"test":"abc"}`)
var f interface{}
json.Unmarshal(bytes, &f)
fmt.Println(f)
}

上面的执行结果是 map[test:abc]

所以可以将原 struct 转为 map[string]interface{},将需要的字段保存入新的 map[string]interface{},然后转为 json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type obj map[string]interface{}

func trim(source obj, target obj) obj {
out := make(obj)
for k, v := range source {
if vv, find := target[k]; find {
switch vv.(type) {
case obj:
out[k] = make(obj)
if len(vv.(obj)) == 0 {
out[k] = v
} else {
out[k] = trim(v.(map[string]interface{}), vv.(obj))
}
case []obj:
if len(vv.([]obj)) == 0 {
out[k] = v
} else {
out[k] = make([]obj, 0)
for i := range v.([]interface{}) {
o := trim(v.([]interface{})[i].(map[string]interface{}), vv.([]obj)[0])
out[k] = append(out[k].([]obj), o)
}
}
default:
out[k] = v
}
}
}
return out
}

trim()source 参数是原始 struct,target 参数中只保留需要的字段。原始 struct 和 target 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type T1 struct {
Field1 string `json:"field_1"`
Field2 []int `json:"field_2"`
Field3 int `json:"field_3"`
}
type T2 struct {
Field1 string `json:"field_1"`
Field2 []int `json:"field_2"`
Field3 int `json:"field_3"`
T1 T1 `json:"t_1"`
}
// 原始 struct
type ComplexStruct struct {
T1
Array []T1 `json:"array"`
T2 T2 `json:"t_2"`
}
// target 中记录需要的 key,值为对象时使用 obj{},值为对象数组时用 []obj{},其他类型用""即可
target := obj{
"array": []obj{
{
"field_1": "",
"field_3": "",
},
},
"t_2": obj{
"t_1": obj{},
},
}

trim() 的返回值是只保留需要字段的 map[string]interface{},再将其 json.Marshal()即可

1
2
3
out := trim(source, target)
bytes, _ = json.MarshalIndent(out, "", " ")
fmt.Println(string(bytes))

完整代码请看 gistplay (只是 demo,没经过大量测试)。使用这种方法只需要定义出 target,不需要像上两个方法那样重新定义 struct,代码会更简洁一些。